/*-------------------------------------------------------------------------
 *
 * tsvector_parser.c
 *	  tsvector 的解析器
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/tsvector_parser.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "tsearch/ts_locale.h"
#include "tsearch/ts_utils.h"


/* 
 * tsvector 解析器的私有状态。请注意，tsquery 也使用此代码来解析其输入，因此有布尔标志。这两个标志在当前用法中都是 true 或都是 false，但我们保持它们分开以保持清晰。
 * is_tsquery 仅影响错误消息的内容。
 */
struct TSVectorParseStateData
{
	char	   *prsbuf;			/* 下一个输入字符 */
	char	   *bufstart;		/* 整个字符串（仅用于错误） */
	char	   *word;			/* 用于保存当前单词的缓冲区 */
	int			len;			/* 为“word”分配的字节大小 */
	int			eml;			/* 每个字符的最大字节数 */
	bool		oprisdelim;		/* 将 ! | * ( ) 视为分隔符？ */
	bool		is_tsquery;		/* 在错误中说“tsquery”而不是“tsvector”？ */
	bool		is_web;			/* 我们在 websearch_to_tsquery() 中 */
};


/*
 * 初始化输入字符串的解析器。如果 oprisdelim 被设置，则下列字符除了空格外也被视为分隔符：
 * ! | & ( )
 */
TSVectorParseState
init_tsvector_parser(char *fc_input, int fc_flags)
{
	TSVectorParseState fc_state;

	fc_state = (TSVectorParseState) palloc(sizeof(struct TSVectorParseStateData));
	fc_state->prsbuf = fc_input;
	fc_state->bufstart = fc_input;
	fc_state->len = 32;
	fc_state->word = (char *) palloc(fc_state->len);
	fc_state->eml = pg_database_encoding_max_length();
	fc_state->oprisdelim = (fc_flags & P_TSV_OPR_IS_DELIM) != 0;
	fc_state->is_tsquery = (fc_flags & P_TSV_IS_TSQUERY) != 0;
	fc_state->is_web = (fc_flags & P_TSV_IS_WEB) != 0;

	return fc_state;
}

/*
 * 重新初始化解析器以解析 'input'，而不是之前的输入。
 */
void reset_tsvector_parser(TSVectorParseState fc_state, char *fc_input)
{
	fc_state->prsbuf = fc_input;
}

/*
 * 关闭 tsvector 解析器。
 */
void close_tsvector_parser(TSVectorParseState fc_state)
{
	pfree(fc_state->word);
	pfree(fc_state);
}

/* 增加 'word' 的大小以便容纳一个更多字符（如果需要） */
#define RESIZEPRSBUF \
do { \
	int fc_clen = fc_curpos - fc_state->word; \
	if ( fc_clen + fc_state->eml >= fc_state->len ) \
	{ \
		fc_state->len *= 2; \
		fc_state->word = (char *) repalloc(fc_state->word, fc_state->len); \
		fc_curpos = fc_state->word + fc_clen; \
	} \
} while (0)

/* 填充 gettoken_tsvector 的输出参数，并返回 true */
#define RETURN_TOKEN \
do { \
	if (fc_pos_ptr != NULL) \
	{ \
		*fc_pos_ptr = fc_pos; \
		*fc_poslen = fc_npos; \
	} \
	else if (fc_pos != NULL) \
		pfree(fc_pos); \
	\
	if (fc_strval != NULL) \
		*fc_strval = fc_state->word; \
	if (fc_lenval != NULL) \
		*fc_lenval = fc_curpos - fc_state->word; \
	if (fc_endptr != NULL) \
		*fc_endptr = fc_state->prsbuf; \
	return true; \
} while(0)


 /* 在 gettoken_tsvector 中使用的状态代码 */
#define WAITWORD		1
#define WAITENDWORD		2
#define WAITNEXTCHAR	3
#define WAITENDCMPLX	4
#define WAITPOSINFO		5
#define INPOSINFO		6
#define WAITPOSDELIM	7
#define WAITCHARCMPLX	8

#define PRSSYNTAXERROR fc_prssyntaxerror(fc_state)

static void fc_prssyntaxerror(TSVectorParseState fc_state)
{
	ereport(ERROR,
			(errcode(ERRCODE_SYNTAX_ERROR),
			 fc_state->is_tsquery ?
			 errmsg("syntax error in tsquery: \"%s\"", fc_state->bufstart) :
			 errmsg("syntax error in tsvector: \"%s\"", fc_state->bufstart)));
}


 /*
 * 从正在解析的字符串中获取下一个令牌。如果成功返回 true，
 * 如果到达输入字符串的末尾，则返回 false。在成功时，这些输出
 * 参数将被填充：
 *
 * *strval		指向令牌的指针
 * *lenval		*strval 的长度
 * *pos_ptr		指向与令牌关联的已分配数组的位置和权重的指针。
 *				如果调用者对信息不感兴趣，可以提供 NULL。
 *				否则，调用者负责释放该数组的内存。
 * *poslen		*pos_ptr 中的元素数量
 * *endptr		扫描恢复点
 *
 * 对于不需要的输出参数传递 NULL。
 */
bool gettoken_tsvector(TSVectorParseState fc_state,
				  char **fc_strval, int *fc_lenval,
				  WordEntryPos **fc_pos_ptr, int *fc_poslen,
				  char **fc_endptr)
{
	int			fc_oldstate = 0;
	char	   *fc_curpos = fc_state->word;
	int			fc_statecode = WAITWORD;

	 /*
	 * pos 用于收集以逗号分隔的位置列表，后面跟着
	 * 实际的令牌。
	 */
	WordEntryPos *fc_pos = NULL;
	int			fc_npos = 0;		 /* 使用的 pos 元素 */
	int			fc_posalen = 0;	 /* pos 的已分配大小 */

	while (1)
	{
		if (fc_statecode == WAITWORD)
		{
			if (*(fc_state->prsbuf) == '\0')
				return false;
			else if (!fc_state->is_web && t_iseq(fc_state->prsbuf, '\''))
				fc_statecode = WAITENDCMPLX;
			else if (!fc_state->is_web && t_iseq(fc_state->prsbuf, '\\'))
			{
				fc_statecode = WAITNEXTCHAR;
				fc_oldstate = WAITENDWORD;
			}
			else if ((fc_state->oprisdelim && ISOPERATOR(fc_state->prsbuf)) ||
					 (fc_state->is_web && t_iseq(fc_state->prsbuf, '"')))
				PRSSYNTAXERROR;
			else if (!t_isspace(fc_state->prsbuf))
			{
				COPYCHAR(fc_curpos, fc_state->prsbuf);
				fc_curpos += pg_mblen(fc_state->prsbuf);
				fc_statecode = WAITENDWORD;
			}
		}
		else if (fc_statecode == WAITNEXTCHAR)
		{
			if (*(fc_state->prsbuf) == '\0')
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("there is no escaped character: \"%s\"",
								fc_state->bufstart)));
			else
			{
				RESIZEPRSBUF;
				COPYCHAR(fc_curpos, fc_state->prsbuf);
				fc_curpos += pg_mblen(fc_state->prsbuf);
				Assert(fc_oldstate != 0);
				fc_statecode = fc_oldstate;
			}
		}
		else if (fc_statecode == WAITENDWORD)
		{
			if (!fc_state->is_web && t_iseq(fc_state->prsbuf, '\\'))
			{
				fc_statecode = WAITNEXTCHAR;
				fc_oldstate = WAITENDWORD;
			}
			else if (t_isspace(fc_state->prsbuf) || *(fc_state->prsbuf) == '\0' ||
					 (fc_state->oprisdelim && ISOPERATOR(fc_state->prsbuf)) ||
					 (fc_state->is_web && t_iseq(fc_state->prsbuf, '"')))
			{
				RESIZEPRSBUF;
				if (fc_curpos == fc_state->word)
					PRSSYNTAXERROR;
				*(fc_curpos) = '\0';
				RETURN_TOKEN;
			}
			else if (t_iseq(fc_state->prsbuf, ':'))
			{
				if (fc_curpos == fc_state->word)
					PRSSYNTAXERROR;
				*(fc_curpos) = '\0';
				if (fc_state->oprisdelim)
					RETURN_TOKEN;
				else
					fc_statecode = INPOSINFO;
			}
			else
			{
				RESIZEPRSBUF;
				COPYCHAR(fc_curpos, fc_state->prsbuf);
				fc_curpos += pg_mblen(fc_state->prsbuf);
			}
		}
		else if (fc_statecode == WAITENDCMPLX)
		{
			if (!fc_state->is_web && t_iseq(fc_state->prsbuf, '\''))
			{
				fc_statecode = WAITCHARCMPLX;
			}
			else if (!fc_state->is_web && t_iseq(fc_state->prsbuf, '\\'))
			{
				fc_statecode = WAITNEXTCHAR;
				fc_oldstate = WAITENDCMPLX;
			}
			else if (*(fc_state->prsbuf) == '\0')
				PRSSYNTAXERROR;
			else
			{
				RESIZEPRSBUF;
				COPYCHAR(fc_curpos, fc_state->prsbuf);
				fc_curpos += pg_mblen(fc_state->prsbuf);
			}
		}
		else if (fc_statecode == WAITCHARCMPLX)
		{
			if (!fc_state->is_web && t_iseq(fc_state->prsbuf, '\''))
			{
				RESIZEPRSBUF;
				COPYCHAR(fc_curpos, fc_state->prsbuf);
				fc_curpos += pg_mblen(fc_state->prsbuf);
				fc_statecode = WAITENDCMPLX;
			}
			else
			{
				RESIZEPRSBUF;
				*(fc_curpos) = '\0';
				if (fc_curpos == fc_state->word)
					PRSSYNTAXERROR;
				if (fc_state->oprisdelim)
				{
					 /* state->prsbuf+=pg_mblen(state->prsbuf); */
					RETURN_TOKEN;
				}
				else
					fc_statecode = WAITPOSINFO;
				continue;		 /* 重新检查当前字符 */
			}
		}
		else if (fc_statecode == WAITPOSINFO)
		{
			if (t_iseq(fc_state->prsbuf, ':'))
				fc_statecode = INPOSINFO;
			else
				RETURN_TOKEN;
		}
		else if (fc_statecode == INPOSINFO)
		{
			if (t_isdigit(fc_state->prsbuf))
			{
				if (fc_posalen == 0)
				{
					fc_posalen = 4;
					fc_pos = (WordEntryPos *) palloc(sizeof(WordEntryPos) * fc_posalen);
					fc_npos = 0;
				}
				else if (fc_npos + 1 >= fc_posalen)
				{
					fc_posalen *= 2;
					fc_pos = (WordEntryPos *) repalloc(fc_pos, sizeof(WordEntryPos) * fc_posalen);
				}
				fc_npos++;
				WEP_SETPOS(fc_pos[fc_npos - 1], LIMITPOS(atoi(fc_state->prsbuf)));
				 /* 我们不能在 tsquery 中到达这里，因此不需要两个错误消息 */
				if (WEP_GETPOS(fc_pos[fc_npos - 1]) == 0)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("wrong position info in tsvector: \"%s\"",
									fc_state->bufstart)));
				WEP_SETWEIGHT(fc_pos[fc_npos - 1], 0);
				fc_statecode = WAITPOSDELIM;
			}
			else
				PRSSYNTAXERROR;
		}
		else if (fc_statecode == WAITPOSDELIM)
		{
			if (t_iseq(fc_state->prsbuf, ','))
				fc_statecode = INPOSINFO;
			else if (t_iseq(fc_state->prsbuf, 'a') || t_iseq(fc_state->prsbuf, 'A') || t_iseq(fc_state->prsbuf, '*'))
			{
				if (WEP_GETWEIGHT(fc_pos[fc_npos - 1]))
					PRSSYNTAXERROR;
				WEP_SETWEIGHT(fc_pos[fc_npos - 1], 3);
			}
			else if (t_iseq(fc_state->prsbuf, 'b') || t_iseq(fc_state->prsbuf, 'B'))
			{
				if (WEP_GETWEIGHT(fc_pos[fc_npos - 1]))
					PRSSYNTAXERROR;
				WEP_SETWEIGHT(fc_pos[fc_npos - 1], 2);
			}
			else if (t_iseq(fc_state->prsbuf, 'c') || t_iseq(fc_state->prsbuf, 'C'))
			{
				if (WEP_GETWEIGHT(fc_pos[fc_npos - 1]))
					PRSSYNTAXERROR;
				WEP_SETWEIGHT(fc_pos[fc_npos - 1], 1);
			}
			else if (t_iseq(fc_state->prsbuf, 'd') || t_iseq(fc_state->prsbuf, 'D'))
			{
				if (WEP_GETWEIGHT(fc_pos[fc_npos - 1]))
					PRSSYNTAXERROR;
				WEP_SETWEIGHT(fc_pos[fc_npos - 1], 0);
			}
			else if (t_isspace(fc_state->prsbuf) ||
					 *(fc_state->prsbuf) == '\0')
				RETURN_TOKEN;
			else if (!t_isdigit(fc_state->prsbuf))
				PRSSYNTAXERROR;
		}
		else					/* 内部错误 */
			elog(ERROR, "unrecognized state in gettoken_tsvector: %d",
				 fc_statecode);

		/* 获取下一个字符 */
		fc_state->prsbuf += pg_mblen(fc_state->prsbuf);
	}
}
